import ij.IJ;
import ij.ImagePlus;
import ij.gui.PolygonRoi;
import ij.gui.Roi;
import ij.plugin.filter.PlugInFilter;
import ij.process.ColorProcessor;
import ij.process.ImageProcessor;

import java.awt.Point;
import java.awt.Rectangle;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.awt.image.renderable.ParameterBlock;

import javax.media.jai.InterpolationBicubic2;
import javax.media.jai.JAI;
import javax.media.jai.PerspectiveTransform;
import javax.media.jai.RenderedOp;
import javax.media.jai.WarpPerspective;


public class Correct_perspective_JAI implements PlugInFilter {

	private ImagePlus _currentIp;
	private Corners _origin;

	public void run(ImageProcessor ip) {
		if (ip == null) {
			return;
		}
		if (_origin != null) {
			startProgress();
			BufferedImage awtImage = imagejToAwt(ip);
			updateProgress();
			Rectangle destiny = getDestinyPoints(_origin);
		    PerspectiveTransform pers = PerspectiveTransform.getQuadToQuad(destiny.x, destiny.y + destiny.height, destiny.x, destiny.y, destiny.x + destiny.width, destiny.y, destiny.x + destiny.width, destiny.y + destiny.height, 
		    		_origin._lowerLeft.x, _origin._lowerLeft.y, _origin._upperLeft.x, _origin._upperLeft.y, _origin._upperRight.x, _origin._upperRight.y, _origin._lowerRight.x, _origin._lowerRight.y); 
		        WarpPerspective warp = new WarpPerspective(pers);
		        ParameterBlock params = new ParameterBlock();
		        params.addSource(awtImage);
		        params.add(warp);
		        params.add(new InterpolationBicubic2(2));
		        correctDestiny(pers, destiny, ip.getWidth(), ip.getHeight());
		        RenderedOp image = JAI.create("warp", params);
		        updateProgress();
		        awtImage = null; //TODO free resource
		        ip = new ColorProcessor(image.getWidth(), image.getHeight());
		        awtToImagej(image.getAsBufferedImage(), ip);
		        _currentIp.setProcessor(_currentIp.getTitle(), ip);
		        updateProgress();
		        _currentIp.setRoi(destiny);
		}
		else {
			IJ.error("The selection must be convex polygon with 4 points!");
		}
	}

	private void correctDestiny(PerspectiveTransform pers, Rectangle destiny, int width, int height) {
		/*
		Point2D newTopLeft = pers.transform(new Point(0, 0), null);
		Point2D newTopRight = pers.transform(new Point(width-1, 0), null);
		Point2D newBottomLeft = pers.transform(new Point(0, height-1), null);
		destiny.x = (int) (destiny.x + Math.max(newTopLeft.getX(),newBottomLeft.getX()));		
		destiny.y = (int) (destiny.y + Math.max(newTopLeft.getY(),newTopRight.getY()));		
		*/
		Point2D newTopLeft = pers.transform(_origin._upperLeft, null);
		destiny.x = (int) newTopLeft.getX();	
		destiny.y = (int) newTopLeft.getY();;		
	}

	int progress = 1;
	
	private void updateProgress() {
		progress++;
		IJ.showProgress(progress, 5);		
	}

	private void startProgress() {
		progress = 1;
		IJ.showProgress(progress, 5);
	}

	private void awtToImagej(BufferedImage image, ImageProcessor ip) {
		int width = image.getWidth();
		int height = image.getHeight();
		for (int x = width-1; x >= 0; x--) {
			for (int y = height-1; y >= 0; y--) {
				ip.set(x, y, image.getRGB(x, y));
			}
		}
	}

	private BufferedImage imagejToAwt(ImageProcessor ip) {
		int width = ip.getWidth();
		int height = ip.getHeight();
		BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
		for (int x = width-1; x >= 0; x--) {
			for (int y = height-1; y >= 0; y--) {
				img.setRGB(x, y, ip.get(x, y));
			}
		}
		return img;
	}

	private Rectangle getDestinyPoints(Corners o) {
		
		/*
		
		d1 = sqrt(pow(x0-x2,2)+pow(y0-y2,2));
		d2 = sqrt(pow(x1-x3,2)+pow(y1-y3,2));
		d3 = sqrt(pow(x0-x1,2)+pow(y0-y1,2));
		d4 = sqrt(pow(x2-x3,2)+pow(y2-y3,2));
		aspect = (d1+d2)/(d3+d4);
		xd0=xd1=x0;
		xd2=xd3=xd0+d1;
		yd0=yd2=y0;
		yd1=yd3=yd0+(d1/aspect);


		xd4=xd5=x1;
		xd6=xd7=x1+d2;
		yd4=yd6=y0;
		yd5=yd7=y0+(d2/aspect);
		
		x0 - lower left
		x1 - upper left
		x2 - lower right
		x3 - upper right
		*/

		/*
		double d1 = Math.sqrt(Math.pow(o._lowerLeft.x-o._lowerRight.x,2) + Math.pow(o._lowerLeft.y-o._lowerRight.y,2));
		double d2 = Math.sqrt(Math.pow(o._upperLeft.x-o._upperRight.x,2) + Math.pow(o._upperLeft.y-o._upperRight.y,2));
		double d3 = Math.sqrt(Math.pow(o._lowerLeft.x-o._upperLeft.x,2) + Math.pow(o._lowerLeft.y-o._upperLeft.y,2));
		double d4 = Math.sqrt(Math.pow(o._lowerRight.x-o._upperRight.x,2) + Math.pow(o._lowerRight.y-o._upperRight.y,2));
		*/
		double d1 = Math.abs(o._lowerLeft.x  - o._lowerRight.x) + Math.abs(o._lowerLeft.y  - o._lowerRight.y);
		double d2 = Math.abs(o._upperLeft.x  - o._upperRight.x) + Math.abs(o._upperLeft.y  - o._upperRight.y);
		double d3 = Math.abs(o._lowerLeft.x  - o._upperLeft.x)  + Math.abs(o._lowerLeft.y  - o._upperLeft.y);
		double d4 = Math.abs(o._lowerRight.x - o._upperRight.x) + Math.abs(o._lowerRight.y - o._upperRight.y);
		
		double aspect = (d1+d2)/(d3+d4);
		
		/*
		xd0=xd1=x0;
		xd2=xd3=xd0+d1;
		yd0=yd2=y0;
		yd1=yd3=yd0+(d1/aspect);


		xd4=xd5=x1;
		xd6=xd7=x1+d2;
		yd4=yd6=y0;
		yd5=yd7=y0+(d2/aspect);
		*/
		
		int x = o._upperLeft.x;
		int y = o._upperLeft.y;
		int width = (int) d1;
		int height = (int) (d1/aspect);
		return new Rectangle(x, y, width, height);
	}

	private Corners getConners(Roi roi) {
		if (roi instanceof PolygonRoi) {
			PolygonRoi polyRoi = (PolygonRoi) roi;
			if ( (polyRoi.getNCoordinates() == 4) && (polyRoi.isArea()) ) {
				int[] Xs = polyRoi.getXCoordinates();
				int[] Ys = polyRoi.getYCoordinates();
				int Xmean = mean(Xs);
				int Ymean = mean(Ys);
				int Ox = polyRoi.getBounds().x;
				int Oy = polyRoi.getBounds().y;
				Point ul = null;
				Point ur = null;
				Point lr = null;
				Point ll = null;
				for (int i = 0; i < Xs.length; i++) {
					int x = Xs[i];
					int y = Ys[i];
					if ( (x < Xmean) && (y < Ymean) && (ul == null) ) {
						ul = new Point(Ox+x,Oy+y);
						continue;
					}
					if ( (x > Xmean) && (y < Ymean) && (ur == null) ) {
						ur = new Point(Ox+x,Oy+y);
						continue;
					}
					if ( (x > Xmean) && (y > Ymean) && (lr == null) ) {
						lr = new Point(Ox+x,Oy+y);
						continue;
					}
					if ( (x < Xmean) && (y > Ymean) && (ll == null) ) {
						ll = new Point(Ox+x,Oy+y);
						continue;
					}
					return null; //the polygon is not convex!
				}
				return new Corners(ul, ur, lr, ll);
			}
		}
		return null;
	}

	private int mean(int[] xs) {
		int mean = 0;
		for (int v : xs) {
			mean += v;
		}
		return mean/xs.length;
	}

	public int setup(String arg, ImagePlus ip) {
		_origin = getConners(ip.getRoi());
		if (_origin == null) {
			IJ.error("The selection must be convex polygon with 4 points!");
			return DONE;
		}
		_currentIp = ip;
		return DOES_RGB + ROI_REQUIRED;
	}
	
	private static class Corners {
		private final Point _upperLeft;
		private final Point _lowerLeft;
		private final Point _upperRight;
		private final Point _lowerRight;
		
		public Corners(Point ul,Point ur,Point lr,Point ll) {
			_upperLeft = ul;
			_lowerLeft = ll;
			_upperRight = ur;
			_lowerRight = lr;
		}
	}

}
